/**
 * copyright Resistor Software <www.resistorsoftware.com>, 2008
 * @author Chris Scott <christocracy@gmail.com>
 * A Shopify Framework form Mootols.  This is the primary file for all Shopify applications built by
 * Resistor Softwrare.  it sets up the namespaces and defines the Application singleton.
 * A contains a mixed bag of other component base-classes, including a form.Field class for rendering fields.
 */

/***
 * console.log fix for machines not running firebug.
 *
 */
if (typeof(console) == 'undefined') {
    var console = {
        log: function() {},
        info: function() {},
        warn: function() {},
        error: function() {},
        time: function() {}
    };
}
else if (typeof(console.log) == 'undefined') {
    console.log = function() {};
}

/**
 * @namespace RMoo
 * @namespace RMoo.util
 * @namespace RMoo.form
 */
var RMoo = {
    util:   {},
    form:   {}
};

/**
 * RMoo.DomHelper
 * @singleton
 * A Dom-building utility.  not yet implemented
 */
RMoo.DomHelper = (function() {

    return {

    }

})();

/**
 * RMoo.ComponentMgr
 * @singleton
 * A component manager.  All instances of RMoo.Component register themselves with this singleton
 * A component can be retrived via RMoo.ComponentMgr.get('component_id');
 */
RMoo.ComponentMgr = (function() {

    var store = new Hash();

    return {
        get : function(id) {
            return store.get(id);
        },

        register : function(cmp) {
            store.set(cmp.id, cmp);
        }
    }
})();
// shortcut RMoo.get -> RMoo.ComponentMgr.get
RMoo.getCmp = RMoo.ComponentMgr.get;

/**
 * @class RMoo.Application
 * @singleton
 * An application singleton
 * @author Chris Scott <christocracy@gmail.com>
 */
RMoo.Application = function() {

    // id seed for auto-generated ids.
    var _idSeed = 1;

    // prefix for autogenerated ids.
    var _idPrefix = 'rmoo-gen';

    // define resources.
    var loading  = null;
    var msgCt = null;
    var queue = [];

    // load resources when dom is ready.
    window.addEvent('domready', function(){
        loading = $('loading');
        loading.set('styles', {
            visibility: 'hidden',
            opacity: 0
        });

        // create the msgBox container.  used for App.setAlert
        msgCt = new Element('div', {id: 'msg-div'});
        msgCt.setStyle('position', 'absolute');
        msgCt.setStyle('z-index', 9999);
        msgCt.setStyle('width', 300);
        msgCt.setStyle('left', '35%');
        msgCt.injectTop($(document.body));
    });

    return {
        /***
         * response status codes.  more to come.  exception only for now.
         */
        STATUS_EXCEPTION :          'EXCEPTION',
        STATUS_VALIDATION_ERROR :   "VALIDATION_ERROR",
        STATUS_ERROR:               "ERROR",
        STATUS_NOTICE:              "NOTICE",
        STATUS_OK:                  "OK",
        STATUS_HELP:                "HELP",

        /** message icons **/
        ICON_ERROR :                'icon-error',
        ICON_OK :                   'icon-accept',
        ICON_NOTICE :               'icon-information',
        ICON_HELP :                 'icon-help',

        /**
         * id
         * generates a new unique id
         */
        id : function(prefix) {
            prefix = prefix || _idPrefix;
            return prefix + (++_idSeed);
        },

        /**
         * setAlert
         * Shows slide-in message box.
         * @param {String} STATUS_CONSTANT
         * @param {String} msg
         */
        setAlert : function(status, msg) {

            var delay = 3;    // <-- default delay of msg box is 1 second.
            if (status == false) {
                delay = 5;    // <-- when status is error, msg box delay is 3 seconds.
            }
            // add some smarts to msg's duration (div by 13.3 between 3 & 9 seconds)
            delay = msg.length / 13.3;
            switch (delay) {
                case delay < 3:
                    delay = 3;
                    break;
                case delay < 3:
                    delay = 5;
                    break;
                case delay > 9:
                    delay = 9;
                    break;
                default:
                    delay = 3;
            }

            var box = this.buildMessageBox(status, msg);
            msgCt.adopt(box);

            var myFx = new Fx.Slide(box, {
                duration: 500
            });
            myFx.hide();    // <-- hide initially

            //Toggles between slideIn and slideOut twice:
            myFx.toggle().chain(
                function() {
                    this.toggle.delay(delay*1000, this);
                },
                function() {
                    this.wrapper.destroy(); // <-- Fx complete.  destroy the Fx wrapper, which includes the message just shown.
                }
            );
        },

        // private buildMessageBox
        buildMessageBox : function(title, msg) {
            switch (title) {
                case true:
                    title = App.STATUS_OK;
                    break;
                case false:
                    title = App.STATUS_ERROR;
                    break;
            }
            var el = new Element('div', {"class": 'msg', id: 'moo-' + $time(), html : [
                '<div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>',
                '<div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc"><h3 class="r-icon-text ' + App._decodeStatusIcon(title) + '">', title, '</h3>', msg, '</div></div></div>',
                '<div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>',
            ].join('')});

            return el;
        },

        // private
        _decodeStatusIcon : function(status) {
            iconCls = '';
            switch (status) {
                case true:
                case App.STATUS_OK:
                    iconCls = App.ICON_OK;
                    break;
                case App.STATUS_NOTICE:
                    iconCls = App.ICON_NOTICE;
                    break;
                case false:
                case App.STATUS_ERROR:
                    iconCls = App.ICON_ERROR;
                    break;
                case App.STATUS_HELP:
                    iconCls = App.ICON_HELP;
                    break;
            }
            return iconCls;
        },

        /**
         * request
         * @param {Object} params same config params defined by Moo's Request
         * The supplied onSuccess method will be hijacked here in order to automate messaging and error-handling
         *
         * A wrapper for Moo's Ajax requests
         */
        request : function(params) {
            var cb = params.onSuccess || null;
            var scope = params.scope || null;
            //params.urlEncoded = false;    // <------------ ??
            App.showSpinner(params.msg);
            params.onSuccess = function(res) {
                App.hideSpinner();
                if (cb != null) {
                    cb.attempt(res, scope);
                }
                App.checkQueue();
            }
            this.addRequest(new Request.JSON(params));
        },

        // private addRequest to queue
        addRequest : function(req) {
            queue.push(req);
            if (queue.length == 1) {
                this.checkQueue();
            }
        },

        // private checkQueue service queued requests
        checkQueue : function() {
            if (queue.length) {
                queue.shift().send();
            }
        },

        /***
         * showSpinner
         * Show the "Loading" message/spinner
         * @param {String} msg ['Loading']
         */
        showSpinner: function(msg) {
            loading.getElement('.loading-indicator').innerHTML = msg || 'loading';
            loading.set('styles', {     // <-- show immediately, with no fade-in.
                visibility: 'visible',
                opacity: 1
            });
        },

        /***
         * hideSpinner
         * hide it.
         */
        hideSpinner : function() {
            loading.fade(0);
        }
    }
}();

// alias App -> RMoo.Application singleton
var App = RMoo.Application

/**
 * @class RMoo.Component
 * A base-class for all widgets
 * @author Chris Scott
 */
RMoo.Component = new Class({

    // options
    options : {
        /**
         * @cfg {String} tag [div]
         * default html tag to render
         */
        tag : 'div',

        /**
         * @cfg cls {String} optional css class
         */
        cls: null,

        /**
         * @cfg style {Object} optional styles to append to Element
         */
        style : null,

        /**
         * @cfg {Number} width
         */
        width: null,

        /**
         * @cfg {Number} height
         */
        height: null,

        /**
         * @cfg {Object} element
         * default element config
         */
        element : {
            "class" : "r-component"
        },

        events : {
            /**
             * @event beforerender {this}
             */
            beforerender : function() {},

            /**
             * @event render {this}
             */
            render : function() {},

            /**
             * @event show {this}
             */
            show : function() {},

            /**
             * @event hide {this}
             */
            hide : function() {}
        },

        /**
         * @cfg {String/id/Element} renderTo
         */
        renderTo: null,

        overCls: null,

        allowDomMove : true,

        /**
         * @cfg {String} html
         */
        html: ''
    },

    // prefix for auto-gen component-ids
    idPrefix : 'rmoo-comp',

    /**
     * @property {Boolean} hidden
     */
    hidden : false,

    /**
     * @property {Element} el
     */
    el : null,

    /**
     * @propery {Boolean} rendered [false]
     */
    rendered : false,

    // private
    container : null,

    // constructor
    initialize : function(params) {
        this.setOptions(params);

        this.addEvents(this.options.events);

        this.initComponent();

        if (this.options.renderTo != null) {
            this.render($(this.options.renderTo));
        }
    },

    // protected
    initComponent : function() {
        this.id = this.options.id || App.id(this.idPrefix);
        delete this.options.id;
        RMoo.ComponentMgr.register(this);
    },

    // protected
    render : function(ct, position) {
        if (!this.rendered) {
            this.fireEvent('beforerender', this);

            this.container = $(ct);

            if(this.ctCls){
                this.container.addClass(this.ctCls);
            }
            this.rendered = true;
            if(position !== undefined){
                if(typeof position == 'number'){
                    position = this.container.childNodes[position];
                }else{
                    position = $(position);
                }
            }
            this.onRender(this.container, position || null);

            /* not yet impl.
            if(this.autoShow){
                this.el.removeClass(['x-hidden','x-hide-' + this.hideMode]);
            }
            */

            if(this.options.cls){
                this.el.addClass(this.options.cls);
                delete this.options.cls;
            }
            if(this.options.style) {
                this.el.set('styles', this.options.style);
                delete this.options.style;
            }

            this.fireEvent('render', this);


            /* not yet impl.
            this.fireEvent("render", this);
            this.afterRender(this.container);
            if(this.hidden){
                this.hide();
            }
            if(this.disabled){
                this.disable();
            }
            this.initStateEvents();
            */


        }
        return this;
    },

    onRender : function(ct, position){
        if(this.options.element){
            if(typeof this.options.element == 'string'){
                this.el = document.createElement(this.autoEl);
            }else{
                var opts = this.options.element;
                if (this.options.html != null) { opts.html = this.options.html; }

                this.el = new Element(this.options.tag, opts);
                delete this.options.element;
            }
            if (!this.el.id) {
                this.el.id = App.id();
            }
            if (this.options.width) {
                this.el.set('styles', {
                    width: this.options.width + 'px'
                });
                delete this.options.width;
            }
        }
        if(this.el){
            this.el = $(this.el);
            if(this.allowDomMove !== false){
                ct.adopt(this.el);
            }
            if(this.overCls) {
                //this.el.addClassOnOver(this.overCls);
            }
        }
    },

    /**
     * show
     */
    show : function() {
        if (this.hidden == true) {
            this.el.set('styles', {     // <-- show immediately, with no fade-in.
                visibility: 'visible',
                opacity: 1
            });
            this.hidden = false;
            this.fireEvent('show', this);
        }
    },

    /**
     * hide
     */
    hide : function() {
        if (this.hidden == false) {
            this.el.set('styles', {     // <-- show immediately, with no fade-in.
                visibility: 'hidden',
                opacity: 0
            });
            this.hidden = true;
            this.fireEvent('hide', this);
        }
    }
});
RMoo.Component.implement(new Options);
RMoo.Component.implement(new Events);

/**
 * @class RMoo.Panel
 * A rich component with header, body, footer elements
 * @author Chris SCott
 */
RMoo.Panel = new Class({
    Extends : RMoo.Component,

    // default options
    options: {
        header : true,
        footer : true,
        title: ''
    },

    /**
     * @property {Element} header
     */
    header : null,

    /**
     * @property {Element} body
     */
    body : null,

    /**
     * @proprety {Element} footer
     */
    footer : null,

    // protected
    initComponent : function() {
        this.parent();
    },

    // onRender.  renders Ext-style header, body, footer
    onRender : function(ct, position) {
        this.parent(ct, position);
        var html = this.el.get('html');
        this.el.set('html', '');

        if (this.options.header && this.options.title) {
            this.header = this.el.adopt(new Element('div', {
                "class": 'r-panel-header',
                html: this.options.title
            }));
            delete this.options.header;
            delete this.options.title;
        }
        this.body = this.el.adopt(new Element('div', {
            "class" : 'r-panel-body',
            html: html
        }));
        this.footer = this.el.adopt(new Element('div', {
            "class" : 'r-panel-footer'
        }));
        this.el.addClass('r-panel');
    }
});

/**
 * RMoo.Window
 * A window implementation in Moo, based upon Ext.Windowd
 */
RMoo.Window = new Class({
    Extends: RMoo.Panel,

    options: {
        /**
         * @cfg {Boolean} constrain [true]
         */
        constrain: true,

        /**
         * @cfg {Boolean} constrainHeader [true]
         */
        constrainHeader: true,

        /**
         * @cfg {Boolean} modal [false]
         */
        modal: false,

        /**
         * @cfg {Boolean} draggable [false]
         */
        draggable: false,

        /**
         * @cfg {Boolean} monitorResize [true]
         */
        monitorResize : true,

        /**
         * @cfg {Boolean} resizable [false]
         */
        resizable: false,

        /**
         * @cfg {Boolean} centered [true]
         */
        centered : true,

        /**
         * @cfg {Number} x co-ord to place window
         */
        x : null,

        /**
         * @cfg {Number} y co-ord to place window
         */
        y : null
    },

    _resizeHandler : null,

    initComponent : function() {
        this.parent();
        this._resizeHandler = this.onWindowResize.bind(this);
    },

    onRender : function(ct, position) {
        this.parent(ct, position);
        this.el.addClass('r-window');
        if(this.options.modal){
            this.mask = new Element('div', {"class" : "r-el-mask", events: {
                "click" : this.hide.bind(this)
            }});
            this.container.adopt(this.mask);
            this.mask.set('styles', {
                visibility: 'hidden',
                opacity: 0
            });

            //this.mask = this.container.createChild({cls:"ext-el-mask"}, this.el.dom);
            //this.mask.enableDisplayMode("block");
            //this.mask.hide();
        }
    },

    // private
    beforeShow : function(){
        /* Ext
        delete this.el.lastXY;
        delete this.el.lastLT;
        */

        if(this.options.x == null || this.options.y == null){
            var cdim = this.container.getCoordinates();
            var edim = this.el.getCoordinates();
            this.options.x = (cdim.width / 2) - (edim.width / 2);
            this.options.y = (cdim.height / 2) - (edim.height / 2);

            /*
            var xy = this.el.getAlignToXY(this.container, 'c-c');
            var pos = this.el.translatePoints(xy[0], xy[1]);
            this.options.x = this.options.x === undefined? pos.left : this.options.x;
            this.options.y = this.options.y === undefined? pos.top : this.options.y;
            */
        }

        this.el.set('styles', {
            left : this.options.x,
            top: this.options.y
        });
        /*
        this.el.setLeftTop(this.options.x, this.options.y);

        if(this.expandOnShow){
            this.expand(false);
        }
        */
        if(this.options.modal){
            //Ext.getBody().addClass("x-body-masked");
            var body = $(document.body);
            var dims = body.getCoordinates();
            this.mask.set('styles', {
                height: dims.height,
                width: dims.width,
                visibility: 'visible',
                opacity: 0.5
            });
        }
    },

    /**
     * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
     * @param {String/Element} animateTarget (optional) The target element or id from which the window should
     * animate while opening (defaults to null with no animation)
     * @param {Function} callback (optional) A callback function to call after the window is displayed
     * @param {Object} scope (optional) The scope in which to execute the callback
     */
    show : function(){
        if(!this.rendered){
            this.render(document.body);
        }
        if (this.options.monitorResize) {
            $(window).addEvent('resize', this._resizeHandler);
        }

        /*
        if(this.hidden === false){
            this.toFront();
            return;
        }

        if(this.fireEvent("beforeshow", this) === false){
            return;
        }
        if(cb){
            this.on('show', cb, scope, {single:true});
        }
        */
        this.beforeShow();
        this.parent();
        this.afterShow();

    },

    // private
    afterShow : function(){
        /*
        this.proxy.hide();
        this.el.setStyle('display', 'block');
        this.el.show();
        if(this.maximized){
            this.fitContainer();
        }
        if(Ext.isMac && Ext.isGecko){ // work around stupid FF 2.0/Mac scroll bar bug
            this.cascade(this.setAutoScroll);
        }

        if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
            Ext.EventManager.onWindowResize(this.onWindowResize, this);
        }
        this.doConstrain();
        if(this.layout){
            this.doLayout();
        }
        if(this.keyMap){
            this.keyMap.enable();
        }
        this.toFront();
        this.updateHandles();
        this.fireEvent("show", this);
        */
    },

    /**
     * Hides the window, setting it to invisible and applying negative offsets.
     * @param {String/Element} animateTarget (optional) The target element or id to which the window should
     * animate while hiding (defaults to null with no animation)
     * @param {Function} callback (optional) A callback function to call after the window is hidden
     * @param {Object} scope (optional) The scope in which to execute the callback
     */
    hide : function(){
        this.parent();
        this.afterHide();
    },

    // private
    afterHide : function(){
        if(this.options.monitorResize || this.options.modal || this.options.constrain || this.options.constrainHeader){
            $(window).removeEvent('resize', this._resizeHandler);
        }
        if(this.options.modal){
            this.mask.set('styles', {
                visibility: 'hidden',
                opacity: 0,
                height: 0,
                width: 0
            });
            //xt.getBody().removeClass("x-body-masked");
        }
        /*
        if(this.keyMap){
            this.keyMap.disable();
        }
        this.fireEvent("hide", this);
        */
    },

    // private
    onWindowResize : function(){
        if(this.options.modal){
            var dims = $(document.body).getCoordinates();
            this.mask.set('styles', {
                height: dims.height - 10,
                width: dims.width - 10
            });
        }
        this.doConstrain();
    },

    // private
    doConstrain : function(){
        if(this.options.constrain || this.options.constrainHeader){
            var offsets;
            if(this.options.constrain){
                offsets = this.el.getCoordinates();
                /*
                offsets = {
                    right:this.el.shadowOffset,
                    left:this.el.shadowOffset,
                    bottom:this.el.shadowOffset
                };
                */
            }else {
                offsets = this.el.getCoordinates();
                /*
                var s = this.getSize();
                offsets = {
                    right:-(s.width - 100),
                    bottom:-(s.height - 25)
                };
                */
            }
            var cdim = this.container.getCoordinates();
            var edim = this.el.getCoordinates();
            this.options.x = (cdim.width / 2) - (edim.width / 2);
            this.options.y = (cdim.height / 2) - (edim.height / 2);
            this.el.set('styles', {
                left : this.options.x,
                top: this.options.y
            });
        }
    },


});
